﻿#If WINDOWS Then
Imports NAudio
Imports NAudio.Wave
#End If
Imports System.Threading
Imports Runa.Foundation.Diagnostics
Imports Microsoft.Xna.Framework.Audio
Imports Microsoft.Xna.Framework.Graphics

Namespace Global.Runa.Resources.ResourceManager

    Public Class AudioObject
        Inherits ResourceObjectBase

        Public Event Completed(ByVal sender As Object, ByVal e As EventArgs(Of Integer))

        Public Property Instance() As SoundEffectInstance

#If WINDOWS Then
        Private WithEvents waveOutDevice As IWavePlayer
        Private volumeStream As WaveStream
#End If

        Public Property IsLoop() As Boolean
            Get
                Return Me.Instance.IsLooped
            End Get
            Set(value As Boolean)
                Me.Instance.IsLooped = value
            End Set
        End Property

        ''' <summary>
        ''' This value must in the range of 0.0 to 1.0.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Volume() As Single
            Get
#If WINDOWS Then
                If Me.IsXnaFormat Then
                    Return Me.Instance.Volume
                Else
                    Dim vs As New WaveChannel32(Me.volumeStream)
                    Return vs.Volume * 100
                End If
#Else
                Return Me.Instance.Volume
#End If
            End Get
            Set(value As Single)
#If WINDOWS Then
                If Me.IsXnaFormat Then
                    Me.Instance.Volume = value
                Else
                    Dim vs As New WaveChannel32(Me.volumeStream)
                    vs.Volume = value / 100
                End If
#Else
                Me.Instance.Volume = value
#End If
            End Set
        End Property

#If WINDOWS Then
        Public ReadOnly Property IsXnaFormat() As Boolean
            Get
                If Me.Instance Is Nothing And Me.waveOutDevice Is Nothing Then
                    Tracer.Default.TraceMessage("Invalid AudioObject source.")
                    Throw New ArgumentException("Invalid AudioObject source.")
                End If
                Return Me.Instance IsNot Nothing
            End Get
        End Property
#End If

#If NETFX_CORE Then
        Private m_threads As New List(Of Tuple(Of Tasks.Task, CancellationTokenSource))
#Else
        Private m_threads As New List(Of MutableTuple(Of System.Threading.Thread, MutableTuple(Of Integer, Object, Boolean)))
#End If

        Public Sub New()
            MyBase.New()
        End Sub

        Public Sub New(ByVal name As String, ByVal fileName As String)
            MyBase.New(name, fileName)
            Me.Instance = Nothing
        End Sub

        Public Sub New(ByVal name As String, ByVal fileName As String, ByVal snd As SoundEffect)
            MyBase.New(name, fileName)
            Me.Instance = snd.CreateInstance()
            Me.volumeStream = Nothing
            Me.IsLoaded = True
        End Sub

#If WINDOWS Then

        Public Sub New(ByVal name As String, ByVal fileName As String, ByVal ws As WaveStream)
            MyBase.New(name, fileName)
            Me.waveOutDevice = New WaveOut(WaveCallbackInfo.FunctionCallback)
            Me.volumeStream = ws
            Me.Instance = Nothing
            Me.IsLoaded = True
        End Sub

#End If

        Public Function Play() As Integer
#If NETFX_CORE Then
            Try
                Dim cts As New CancellationTokenSource()
                Dim thr As New Threading.Tasks.Task(
                    Sub(obj As Object)
                        Dim sndi = CType(obj, Tuple(Of SoundEffectInstance, CancellationTokenSource))
                        sndi.Item1.Play()
                        While sndi.Item1.State = SoundState.Playing
                            sndi.Item2.Token.ThrowIfCancellationRequested()
                            System.Threading.Tasks.Task.Delay(100)
                        End While
                        RaiseEvent Completed(Me, New EventArgs())
                    End Sub, New Tuple(Of SoundEffectInstance, CancellationTokenSource)(Me.Instance, cts))
                Dim tp = New Tuple(Of Tasks.Task, CancellationTokenSource)(thr, cts)
                Me.m_threads.Add(tp)
                thr.Start()
                Return Me.m_threads.IndexOf(tp)
            Catch ex As Exception
                Return -1
            End Try
#Else
            Dim thr As New System.Threading.Thread(
                Sub(obj)
                    Dim parm = CType(obj, MutableTuple(Of Integer, Object, Boolean))
                    Try
                        'SoundEffectInstance
                        If TypeOf parm.Item2 Is SoundEffectInstance Then
                            Dim sndi = CType(parm.Item2, SoundEffectInstance)
                            sndi.Play()
                            While sndi.State = SoundState.Playing Or parm.Item3 = False
                                System.Threading.Thread.Sleep(100)
                            End While
                            RaiseEvent Completed(Me, New EventArgs(Of Integer)(parm.Item1))
#If WINDOWS Then
                        ElseIf TypeOf parm.Item2 Is WaveStream Then
                            Me.PlayNonXnb(parm.Item1, parm.Item2, parm)
                        End If
#End If
                    Catch ex As Exception
                        Tracer.Default.TraceMessage(ex.Message & vbCrLf & ex.StackTrace.ToString())
                        Exit Sub
                    End Try
                    'Me.m_threads.RemoveAt(parm.Item1)
                End Sub)
            Dim key = New MutableTuple(Of Integer, Object, Boolean)(0, If(Me.Instance IsNot Nothing, Me.Instance, Me.volumeStream), False)
            Dim item = New MutableTuple(Of System.Threading.Thread, MutableTuple(Of Integer, Object, Boolean))(thr, key)
            Me.m_threads.Add(item)
            Dim indx = Me.m_threads.IndexOf(item)
            key.Item1 = indx
            thr.Start(key)
            Return indx
#End If
        End Function

        ''' <summary>
        ''' Play the sound in background.
        ''' </summary>
        ''' <remarks></remarks>
        Public Function Play(ByVal isLoop As Boolean, ByVal vol As Single) As Integer
#If NETFX_CORE Then
            Dim cts As New CancellationTokenSource()
            Dim thr As New Threading.Tasks.Task(
                Sub(obj As Object)
                    Try
                        Dim sndi = CType(obj, Tuple(Of SoundEffectInstance, CancellationTokenSource))
                        sndi.Item1.IsLooped = isLoop
                        sndi.Item1.Volume = vol
                        sndi.Item1.Play()
                        While sndi.Item1.State = SoundState.Playing
                            sndi.Item2.Token.ThrowIfCancellationRequested()
                            System.Threading.Tasks.Task.Delay(100)
                        End While
                        RaiseEvent Completed(Me, New EventArgs())
                    Catch ex As Exception
                        Exit Sub
                    End Try
                End Sub, New Tuple(Of SoundEffectInstance, CancellationTokenSource)(Me.Instance, cts))
            Dim tp = New Tuple(Of Tasks.Task, CancellationTokenSource)(thr, cts)
            Me.m_threads.Add(tp)
            thr.Start()
            Return Me.m_threads.IndexOf(tp)
#Else
#If WINDOWS Then
            If Me.IsXnaFormat Then
#End If
                Dim thr As New System.Threading.Thread(
                    Sub(obj)
                        Try
                            Dim parm = CType(obj, Tuple(Of Integer, SoundEffectInstance))
                            Dim sndi = parm.Item2
                            sndi.IsLooped = isLoop
                            sndi.Volume = vol
                            sndi.Play()
                            While sndi.State = SoundState.Playing
                                System.Threading.Thread.Sleep(100)
                            End While
                            RaiseEvent Completed(Me, New EventArgs(Of Integer)(parm.Item1))
                        Catch ex As Exception
                            Tracer.Default.TraceMessage(ex.Message & vbCrLf & ex.StackTrace.ToString())
                            Exit Sub
                        End Try
                    End Sub)
                'Me.m_threads.Add(New MutableTuple(Of System.Threading.Thread, MutableTuple(Of Integer, Object, Boolean))(thr))
                'Dim indx = Me.m_threads.IndexOf(thr)
                'thr.Start(New Tuple(Of Integer, SoundEffectInstance)(indx, Me.Instance))
                'Return indx
                Dim key = New MutableTuple(Of Integer, Object, Boolean)(0, If(Me.Instance IsNot Nothing, Me.Instance, Me.volumeStream), False)
                Dim item = New MutableTuple(Of System.Threading.Thread, MutableTuple(Of Integer, Object, Boolean))(thr, key)
                Me.m_threads.Add(item)
                Dim indx = Me.m_threads.IndexOf(item)
                key.Item1 = indx
                thr.Start(key)
                Return indx
#If WINDOWS Then
            Else
                Return Me.Play()
            End If
#End If
#End If
        End Function

        Public Sub [Stop](Optional ByVal index As Integer = -1, Optional ByVal imediately As Boolean = True)
#If NETFX_CORE Then
            Try
                If index = -1 Then
                    For Each thr In Me.m_threads
                        If thr IsNot Nothing Then
                            thr.Item2.Cancel()
                        End If
                    Next
                    Me.Instance.Stop(imediately)
                    Me.m_threads.Clear()
                Else
                    Me.m_threads(index).Item2.Cancel()
                    Me.m_threads(index) = Nothing
                End If
            Catch ex As Exception
                Me.m_threads.Clear()
                Exit Sub
            End Try
#Else
            Try
                If index = -1 Then
                    For Each thr In Me.m_threads
                        Try
                            If thr IsNot Nothing Then
                                'thr.Item1.Abort()
                                thr.Item2.Item3 = True
                            End If
                        Catch ex As Exception
                            Tracer.Default.TraceMessage(ex.Message & vbCrLf & ex.StackTrace.ToString())
                            Exit Try
                        End Try
                    Next
#If WINDOWS Then
                    If Me.IsXnaFormat Then
#End If
                        Me.Instance.Stop(imediately)
#If WINDOWS Then
                    Else
                        Me.waveOutDevice.Stop()
                    End If
#End If
                    Me.m_threads.Clear()
                Else
                    'Me.m_threads(index).Item1.Abort()
                    Me.m_threads(index).Item2.Item3 = True
                    Me.m_threads(index) = Nothing
                End If
            Catch ex As Exception
                Tracer.Default.TraceMessage(ex.Message & vbCrLf & ex.StackTrace.ToString())
                Me.m_threads.Clear()
                Exit Sub
            End Try
#End If
        End Sub

        Public Overrides Function Load(g As Microsoft.Xna.Framework.Game) As Boolean
            If Me.IsLoaded Then Return True
            Me.Instance = g.Content.Load(Of SoundEffect)(Me.FileName).CreateInstance()
            Me.IsLoaded = True
            Return True
        End Function

        Public Shared Function LoadFromData(ByVal name As String, ByVal filename As String, ByVal data() As Byte) As AudioObject
            Using ms As New MemoryStream(data)
                Return AudioObject.LoadFromStream(name, filename, ms)
            End Using
        End Function

        Public Shared Function LoadFromStream(ByVal name As String, ByVal filename As String, ByVal stream As Stream) As AudioObject
            Try
                Dim se = SoundEffect.FromStream(stream)
                Return New AudioObject(name, filename, se)
            Catch ex As Exception
                ' If the asset is another type e.g. MP3, WAV...
                Try
                    Dim vs = LoadNonXnbStream(stream)
                    Return New AudioObject(name, filename, vs)
                Catch _ex As Exception
                    Dim msg = filename & vbCrLf & _ex.Message & vbCrLf & _ex.StackTrace.ToString()
                    Tracer.Default.TraceMessage(msg)
                    Throw New ArgumentException(msg)
                End Try
            End Try
        End Function

        Private Shared Function LoadNonXnbStream(ByVal stream As System.IO.Stream) As WaveStream
            'Me.waveOutDevice = New WaveOut()
            Dim i As Integer = 0
            Dim ws As WaveStream = Nothing
            While True
                Try
                    Select Case i
                        Case 0
                            ws = New Mp3FileReader(stream)
                        Case 1
                            ws = New WaveFileReader(stream)
                        Case 2
                            ws = New AiffFileReader(stream)
                        Case Else
                            Dim msg = "Invalid audio file format."
                            Throw New ArgumentException(msg)
                    End Select
                    Exit While
                Catch ex As Exception
                    i += 1
                End Try
            End While
            Return WaveFormatConversionStream.CreatePcmStream(ws)
        End Function

        Protected Overrides Sub Dispose(disposing As Boolean)
            Me.Stop()
            If Me.IsXnaFormat Then
                Me.Instance.Dispose()
            Else
                Me.volumeStream.Dispose()
                Me.waveOutDevice.Dispose()
            End If
            MyBase.Dispose(disposing)
        End Sub

        ''' <summary>
        ''' Play non XNB audio files.
        ''' For PC only.
        ''' Running on apart thread.
        ''' </summary>
        ''' <param name="index"></param>
        ''' <param name="wchannel"></param>
        ''' <remarks></remarks>
        Private Sub PlayNonXnb(ByVal index As Integer, ByVal wchannel As WaveStream, ByVal parm As MutableTuple(Of Integer, Object, Boolean))
            Try
                If Me.waveOutDevice Is Nothing Then
                    Me.waveOutDevice = New WaveOut(WaveCallbackInfo.FunctionCallback)
                    Tracer.Default.TraceMessage("The device has not been ready until we need it.")
                Else
                    Me.waveOutDevice.Stop()
                End If
                Dim baStream As New BlockAlignReductionStream(wchannel)
                Me.waveOutDevice.Init(baStream)
                Me.waveOutDevice.Play()
                While Me.waveOutDevice.PlaybackState = PlaybackState.Playing Or parm.Item3 = False
                    Thread.Sleep(100)
                End While
            Catch ex As Exception
                Exit Sub
            End Try
        End Sub

        Private Sub waveOutDevice_PlaybackStopped(sender As Object, e As NAudio.Wave.StoppedEventArgs) Handles waveOutDevice.PlaybackStopped
            If e.Exception IsNot Nothing Then
                Tracer.Default.TraceMessage(e.Exception.Message & vbCrLf & e.Exception.StackTrace.ToString())
            End If
            Me.Stop()
            'Me.volumeStream.Position = 0
            RaiseEvent Completed(Me, New EventArgs(Of Integer)())
        End Sub

    End Class

End Namespace